package com.better.common.jwtauth.secruity;

import com.better.common.jwtauth.TokenManager.RedisTokenInfo;
import com.better.common.jwtauth.TokenManager.RedisTokenManager;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@SuppressWarnings("SpringJavaAutowiringInspection")
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("${jwt.header}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private RedisTokenManager redisTokenManager;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(this.tokenHeader);
        if (StringUtils.isNotEmpty(authHeader) && authHeader.startsWith(tokenHead)) {
            final String authToken = authHeader.substring(tokenHead.length()); // The part after "Bearer "
            //从redis 获取真正的有效token
            RedisTokenInfo realToken = redisTokenManager.getRealToken(authToken);
            if (realToken != null) {
                //验证真实token 是否过期,如果没有过期，更新延期失效时间，如果过期重新生成token ,并设置延期时间
                String username = jwtTokenUtil.getUsernameFromToken(realToken.getToken());
                if (StringUtils.isNotEmpty(username) && SecurityContextHolder.getContext().getAuthentication() == null) {
                    JWTUserDetails userDetails = (JWTUserDetails) this.userDetailsService.loadUserByUsername(username);
                    if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                        setAuthentication(request, userDetails);
                        RedisTokenInfo redisTokenInfo = new RedisTokenInfo(username, authToken);
                        //刷新有效时间
                        redisTokenManager.refreshToken(authToken, redisTokenInfo);
                    }
                } else if (StringUtils.isEmpty(username)) {//
                    JWTUserDetails userDetails = (JWTUserDetails) this.userDetailsService.loadUserByUsername(realToken.getUserName());
                    String refreshToken = redisTokenManager.generateRefreshToken(userDetails);
                    if (StringUtils.isNotEmpty(refreshToken)) {
                        setAuthentication(request, userDetails);
                        RedisTokenInfo redisTokenInfo = new RedisTokenInfo(realToken.getUserName(), authToken);
                        //刷新有效时间
                        redisTokenManager.refreshToken(authToken, redisTokenInfo);
                    }
                }
            }
        }
        chain.doFilter(request, response);
    }

    /**
     * 验证通过后需要设置spring secruity context ,便于应用后续获取登录用户信息
     *
     * @author:
     * @date :2017/12/13
     */
    private void setAuthentication(HttpServletRequest request, JWTUserDetails userDetails) {
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                request));
        logger.info("authenticated user " + userDetails.getUsername() + ", setting security context");
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }


}
